<?php
include_once("ExtArray.php");
include_once("DB.php");

class DBTable
{
	public $name;			// String name of table	
	public $columns;		// EArray of Column objects
	
	public function __construct()
	{
		$this->columns = new EArray();
	}
	
	public function columns($cols=null)
	{
		if($cols == null)
			return $this->columns;
		else
			$this->columns = $cols;
	}
	
	public function save($table)
	{
		$obj = new ReflectionObject($table);
		$name = $obj->getName();

		
		/**** Naive Approach ****/
		
		$upList = "";
		$upListArr = array();
		$pk = null;
		$pkProp = null;

		foreach($obj->getProperties() as $prop)
		{
			$propName = $prop->getName();
			
			if(strpos($propName, "_") === 0)
			{
				$propName = substr_replace($propName, "", 0, 1);
				
				if(array_key_exists($propName, $table->meta))
				{
					$prop->setAccessible(true);
					$upListArr[$propName] = $prop->getValue($table);

					if($table->meta[$propName]["primary"])
					{
						$pk = $propName;
						$pkProp = $prop;
					}
				}
			}
		}

		$statement = "";
		
		if($upListArr[$pk] == null)			// Do insert
		{
			$statement = "INSERT INTO `{$name}` (#INSLIST#) VALUES (#VALLIST#);";
			$valList = "";
			
			$count = 0;
			foreach($upListArr as $key=>$val)
			{
				if(!$table->meta[$key]["primary"])
				{
					if(empty($val) && !$table->meta[$key]["nullable"])
						throw new Exception("Attribute: '$key' cannot be null.");

					$upList .= "`$key`";
					$valList .= "'$val'";
				
					if($count < count($upListArr) - 1)
					{
						$upList .= ", ";
						$valList .= ", ";
					}
				}

				$count++;
			}
			
			$statement = str_replace("#INSLIST#", $upList, $statement);
			$statement = str_replace("#VALLIST#", $valList, $statement);
		}
		else					// Do update
		{
			$statement = "UPDATE `{$name}` SET #UPLIST# WHERE `#PK#`='#CONDITION#';";
			$condition = "";
			
			$count = 0;
			foreach($upListArr as $key=>$val)
			{
				if(!$table->meta[$key]["primary"])
				{
					if(empty($val) && !$table->meta[$key]["nullable"])
						throw new Exception("Attribute: '$key' cannot be null.");
					
					$upList .= "`$key`='$val'";
									
					if($count < count($upListArr) - 1)
						$upList .= ", ";
				}
				else
				{
					$pk = $key;
					$condition = $val;
				}

				$count++;
			}
			
			$statement = str_replace("#UPLIST#", $upList, $statement);
			$statement = str_replace("#PK#", $pk, $statement);
			$statement = str_replace("#CONDITION#", $condition, $statement);
		}
		
		$db = new DB();

		if($upListArr[$pk] == null)		// A new insert
		{
			$result = $db->execute($statement);
			$pkProp->setValue($table, $db->last_insert());
		}
		else
		{
			$db->execute($statement);
		}
		
		return $this;
	}
	
	public function where($table, $fields, $criteria)
	{
		$obj = new ReflectionObject($table);
		$name = $obj->getName();
		$statement = "SELECT * FROM {$name} WHERE #FCRIT#;";

		if(is_array($fields) && is_array($criteria))		// Do multiple where search
		{
			if(count($fields) == 1 && count($criteria) == 1)	// Do single where
			{
				$fCrit = "`{$fields[0]}`='{$criteria[0]}'";
				$statement = str_replace("#FCRIT#", $fCrit, $statement);
			}
			else
			{
				if(count($fields) != count($criteria))
					throw new Exception("Count in fields list does not match count in criteria list");
				
				$fCrit = "";
				for($i = 0; $i < count($fields); $i++)
				{
					$fCrit .= "`{$fields[$i]}`='{$criteria[$i]}'";
					if($i < count($fields) - 1)
						$fCrit .= ", ";					
				}
				
				$statement = str_replace("#FCRIT#", $fCrit, $statement);
			}
		}
		else												// Do single where search
		{
			$fCrit = "`{$fields}`='{$criteria}'";
			$statement = str_replace("#FCRIT#", $fCrit, $statement);
		}
		
		$db = new DB();
		$result = $db->execute_return($statement);
		
		// Now populate the new objects
		if(count($result) == 1)
		{
			$newObj = new $name;
			$obj = new ReflectionObject($newObj);
			$objProps = $obj->getProperties();
			
			foreach($objProps as $prop)
			{
				$propName = $prop->getName();
				if(strpos($propName, "_") === 0)
				{
					$propName = substr_replace($propName, "", 0, 1);
					
					if(array_key_exists($propName, $result[0]))
					{
						$prop->setAccessible(true);
						$prop->setValue($newObj, $result[0][$propName]);
					}
				}
			}
			
			return $newObj;
			
			/*foreach($result as $key=>$val)
			{
				if(array_key_exists($key, $table->meta))
				{
					
				}
			}*/
			
			
			
			/* WORKING HERE */
			/*foreach($obj->getProperties() as $prop)
			{
				$propName = $prop->getName();
				
				if(strpos($propName, "_") === 0)
				{
					$propName = substr_replace($propName, "", 0, 1);
					
					if(array_key_exists($propName, $table->meta))
					{
						$prop->setAccessible(true);
						//$upListArr[$propName] = $prop->getValue($table);
						$pkProp->setValue($table, "");

						if($table->meta[$propName]["primary"])
						{
							$pk = $propName;
							$pkProp = $prop;
						}
					}
				}
			}*/
		}
		else
		{
			$ret = array();
			
			foreach($result as $res)
			{
				$newObj = new $name;
				$obj = new ReflectionObject($newObj);
				$objProps = $obj->getProperties();
				
				foreach($objProps as $prop)
				{
					$propName = $prop->getName();
					if(strpos($propName, "_") === 0)
					{
						$propName = substr_replace($propName, "", 0, 1);
						
						if(array_key_exists($propName, $res))
						{
							$prop->setAccessible(true);
							$prop->setValue($newObj, $res[$propName]);
						}
					}
				}
				
				//return $newObj;
				$ret[] = $newObj;
			}
			
			return $ret;
		}
	}
}

class DBColumn
{
	public $attr;		// String name of column
	public $type;		// DBType "enum"
	public $size;		// Size of column
	public $isPrimary;	// Is this column make up the primary key
	public $isUnique;	// Is this column unique
	public $isNullable;	// Is this column nullable
}

class DBType
{
	const CHAR = 0;
	const VARCHAR = 1;
	const TINYTEXT = 2;
	const TEXT = 3;
	const BLOB = 4;
	const MEDIUMTEXT = 5;
	const MEDIUMBLOG = 6;
	const LONGTEXT = 7;
	const LONGBLOB = 8;
	
	const TINYINT = 9;
	const SMALLINT = 10;
	const MEDIUMINT = 11;
	const INT = 12;
	const BIGINT = 13;
	const FLOAT = 14;
	const DOUBLE = 15;
	const DECIMAL = 16;
	
	const DATE = 17;
	const DATETIME = 18;
	const TIMESTAMP = 19;
	const TIME = 20;
	
	const ENUM = 21;
	const SET = 22;
}

/*class Date
{
	private $_date;

	public function __construct($date=null)
	{
		if($date != null)
			$this->_date = strtotime($date);
	}
	
	public function FullDateTime($format)
	{
		
	}
}*/


/***************************************************/
/*			My Test DB							   */
/***************************************************/
class person extends DBTable
{
	private $_UserID;		// PK - int
	private $_Name;			// varchar(50)
	private $_Birthdate;	// datetime
	private $_Salary;		// float
	
	protected $meta = 
		array(
			"UserID" => array("type" => "int", "size" => 11, "primary" => true, "unique" => true, "nullable" => false),
			"Name" => array("type" => "varchar", "size" => 50, "primary" => false, "unique" => false, "nullable" => false),
			"Birthdate" => array("type" => "int", "size" => null, "primary" => false, "unique" => false, "nullable" => false),
			"Salary" => array("type" => "int", "size" => null, "primary" => false, "unique" => false, "nullable" => true)
		);

	private $_isSet = false;
	private $_criteria = null;
	
	public function is_set()
	{
		return $this->_isSet;
	}

	public function UserID($userID=null)
	{
		if($userID == null)
			return $this->_UserID;
		else
			$this->_UserID = $userID;
		
		return $this;
	}

	public function Name($val=null)
	{
		if($val === null)	// Getter
		{
			return $this->_Name;
		}
		else			// Setter
		{
			if($this->_Name != $val)
			{
				if(!is_string($val))
					throw new InvalidArgumentException("Type mismatch. Expected: string, actual: " . gettype($val));

				$this->_Name=$val;
			}
		}
		
		return $this;
	}

	public function Birthdate($val=null)
	{
		if($val === null)	// Getter
		{
			return $this->_Birthdate;
		}
		else			// Setter
		{
			if($this->_Birthdate != $val)
			{
				$this->_Birthdate = date("Y-m-d", strtotime($val));
			}
		}
		
		return $this;
	}

	public function Salary($val=null)
	{
		if($val === null)	// Getter
		{
			return $this->_Salary;
		}
		else			// Setter
		{
			if($this->_Salary != $val)
			{
				

				$this->_Salary=$val;
			}
		}
		
		return $this;
	}

	public function __construct()
	{
		$cols = new EArray();
		
		$col = new DBColumn();
		$col->attr = "UserID";
		$col->type = DBType::INT;
		$col->size = 11;
		$col->isPrimary = true;
		$col->isUnique = true;
		$col->isNullable = false;
		$cols->append($col);
		
		$col = new DBColumn();
		$col->attr = "Name";
		$col->type = DBType::VARCHAR;
		$col->size = 50;
		$col->isPrimary = false;
		$col->isUnique = false;
		$col->isNullable = false;
		$cols->append($col);
		
		$col = new DBColumn();
		$col->attr = "Birthdate";
		$col->type = DBType::DATETIME;
		$col->size = null;
		$col->isPrimary = false;
		$col->isUnique = false;
		$col->isNullable = false;
		$cols->append($col);
		
		$col = new DBColumn();
		$col->attr = "Salary";
		$col->type = DBType::FLOAT;
		$col->size = null;
		$col->isPrimary = false;
		$col->isUnique = false;
		$col->isNullable = true;
		$cols->append($col);
		
		parent::columns($cols);
	}

	/*public function single()
	{
		// sql statement to select single element
		$query = "SELECT * FROM `person` WHERE `UserID` = '{$this->_UserID}';";
		$db = new DB();
		$result = $db->execute_return($query);
		
		if(count($result) > 1)
			throw new Exception("Result set contains more than one result");
			
		$result = $result[0];

		$this->_UserID = $result["UserID"];
		$this->_Name = $result["Name"];
		$this->_Birthdate = $result["Birthdate"];
		$this->_Salary = $result["Salary"];
		
		$this->_isSet = true;

		return $this;
	}*/
	
	public function single()
	{
		if($this->_criteria == null || count($this->_criteria) == 0)
			throw new Exception("Cannot call single on null criteria");
		
		$query = "SELECT * FROM `person` WHERE ";
		
		for($i = 0; $i < count($this->_criteria); $i++)
		{
			if(count($this->_criteria) == ($i + 1))
			{
				$query .= "`{$this->_criteria[$i]["fields"]}`='{$this->_criteria[$i]["criteria"]}'";
			}
			else
			{
				$query .= "`{$this->_criteria[$i]["fields"]}`='{$this->_criteria[$i]["criteria"]}', ";
			}
		}
		
		$query .= ";";
		
		$db = new DB();
		$result = $db->execute_return($query);
		
		if(count($result) > 1)
			throw new Exception("Result set contains more than one result");
			
		$result = $result[0];

		$this->_UserID = $result["UserID"];
		$this->_Name = $result["Name"];
		$this->_Birthdate = $result["Birthdate"];
		$this->_Salary = $result["Salary"];
		
		$this->_isSet = true;

		return $this;
	}
	
	public function where($fields, $criteria)
	{
		if($this->_criteria == null)
		{
			for($i = 0; $i < count($fields); $i++)
			{
				$this->_criteria[] = array("fields" => $fields, "criteria" => $criteria);
			}
		}
		return $this;
	}

	public function save()
	{
		return parent::save($this);
	}
	
	/*public function where($fields, $criteria)
	{
		return parent::where($this, $fields, $criteria);
	}*/
}

class job extends DBTable
{
	private $_JobID;		// PK - int
	private $_Title;		// int(11)
	
	private $_isSet = false;
	protected $meta = 
		array(
			"JobID" => array("type" => "int", "size" => 11, "primary" => true, "unique" => true, "nullable" => false),
			"Title" => array("type" => "varchar", "size" => 50, "primary" => false, "unique" => false, "nullable" => false)
		);
		
	public function is_set()
	{
		return $this->_isSet;
	}
	
	public function JobID($JobID=null)
	{
		if($JobID == null)
			return $this->_JobID;
		else
			$this->_JobID = $JobID;
		
		return $this;
	}

	public function Title($val=null)
	{
		if($val === null)	// Getter
		{
			return $this->_Title;
		}
		else			// Setter
		{
			if($this->_Title != $val)
			{
				if(!is_string($val))
					throw new InvalidArgumentException("Type mismatch. Expected: string, actual: " . gettype($val));

				$this->_Title=$val;
			}
		}
		
		return $this;
	}
	
	public function __construct()
	{
		$cols = new EArray();
		
		$col = new DBColumn();
		$col->attr = "JobID";
		$col->type = DBType::INT;
		$col->size = 11;
		$col->isPrimary = true;
		$col->isUnique = true;
		$col->isNullable = false;
		$cols->append($col);
		
		$col = new DBColumn();
		$col->attr = "Title";
		$col->type = DBType::VARCHAR;
		$col->size = 50;
		$col->isPrimary = false;
		$col->isUnique = false;
		$col->isNullable = false;
		$cols->append($col);
		
		parent::columns($cols);
	}

	public function single()
	{
		// sql statement to select single element
		$query = "SELECT * FROM `job` WHERE `JobID` = '{$this->_JobID}';";
		$db = new DB();
		$result = $db->execute_return($query);
		
		if(count($result) > 1)
			throw new Exception("Result set contains more than one result");
			
		$result = $result[0];

		$this->_JobID = $result["JobID"];
		$this->_Title = $result["Title"];
		
		$this->_isSet = true;

		return $this;
	}

	public function save()
	{
		return parent::save($this);
	}
	
	public function where($fields, $criteria)
	{
		return parent::where($this, $fields, $criteria);
	}
}

class job_person extends DBTable
{
	private $_JobPersonID;		// PK - int
	private $_JobID;			// int(11)
	private $_UserID;			// int(11)
	
	private $_isSet = false;
	protected $meta = 
		array(
			"JobPersonID" => array("type" => "int", "size" => 11, "primary" => true, "unique" => true, "nullable" => false),
			"JobID" => array("type" => "int", "size" => null, "primary" => false, "unique" => true, "nullable" => false),
			"UserID" => array("type" => "int", "size" => null, "primary" => false, "unique" => false, "nullable" => false)
		);
		
	public function is_set()
	{
		return $this->_isSet;
	}
	
	public function JobPersonID($JobID=null)
	{
		if($JobPersonID == null)
			return $this->_JobPersonID;
		else
			$this->_JobPersonID = $JobPersonID;
		
		return $this;
	}
	
	public function JobID($JobID=null)
	{
		if($JobID == null)
			return $this->_JobID;
		else
			$this->_JobID = $JobID;
		
		return $this;
	}
	
	public function UserID($JobID=null)
	{
		if($UserID == null)
			return $this->_UserID;
		else
			$this->_UserID = $UserID;
		
		return $this;
	}
	
	public function __construct()
	{
		$cols = new EArray();
		
		$col = new DBColumn();
		$col->attr = "JobPersonID";
		$col->type = DBType::INT;
		$col->size = 11;
		$col->isPrimary = true;
		$col->isUnique = true;
		$col->isNullable = false;
		$cols->append($col);

		$col = new DBColumn();
		$col->attr = "JobID";
		$col->type = DBType::INT;
		$col->size = 11;
		$col->isPrimary = false;
		$col->isUnique = true;
		$col->isNullable = false;
		$cols->append($col);

		$col = new DBColumn();
		$col->attr = "UserID";
		$col->type = DBType::INT;
		$col->size = 11;
		$col->isPrimary = false;
		$col->isUnique = true;
		$col->isNullable = false;
		$cols->append($col);
		
		parent::columns($cols);
	}

	// Redo single. If criteria is populatled, used that to find object. If not, check to see
	// if the object is set. If it isn't, fetch.	
	public function single()
	{
		// sql statement to select single element
		$query = "SELECT * FROM `job_person` WHERE `JobPersonID` = '{$this->_JobPersonID}';";
		$db = new DB();
		$result = $db->execute_return($query);
		
		if(count($result) > 1)
			throw new Exception("Result set contains more than one result");
			
		$result = $result[0];

		$this->_JobPersonID = $result["JobPersonID"];
		$this->_JobID = $result["JobID"];
		$this->_UserID = $result["UserID"];
		
		$this->_isSet = true;

		return $this;
	}

	public function save()
	{
		return parent::save($this);
	}
	
	public function where($fields, $criteria)
	{
		return parent::where($this, $fields, $criteria);
	}
}

?>